package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IUserConnectionListItem;
import org.infodavid.common.ejb.UserConnectionBean;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserConnection;
import org.infodavid.common.model.behaviors.IApprovable.EApprovalStatus;
import org.infodavid.common.model.criteria.IExtendedSearchCriteria;
import org.infodavid.common.model.criteria.IUserConnectionCriteria;
import org.infodavid.common.model.impl.HibernateHelper;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.UserConnectionDataService;
import org.infodavid.common.persistence.UserSessionDataService;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class UserConnectionDataServiceImpl.
 */
@AImplementation(value = UserConnectionDataService.class, runtime = ERuntime.DEFAULT)
public final class UserConnectionDataServiceImpl extends AbstractUserConnectionDataService {

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(AbstractUserConnectionDataService.class);

	/** The helper. */
	private final DataServiceHelper<IUserConnectionListItem,IUserConnection,Long> helper;

	/**
	 * The Constructor.
	 */
	private UserConnectionDataServiceImpl() {
		super();

		helper = new DataServiceHelper<IUserConnectionListItem,IUserConnection,Long>(this);
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.persistence.IDataService#getEntityClass()
	 */
	public Class<? extends IUserConnection> getEntityClass() {
		return UserConnectionBean.class;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List, java.util.List)
	 */
	public long find(final PersistenceSession session, final List<IUserConnectionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		return helper.findListItems(session, entities, parameters, QUERY_FIND, QUERY_FIND_COUNT);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
	 */
	@SuppressWarnings({
	    "unchecked", "boxing"
	})
	public long find(final PersistenceSession session, final List<IUserConnectionListItem> entities,
	    final IUserConnectionCriteria criteria) throws PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Searching by criteria using session = " + session + ", entities = " + entities
			    + ", criteria = " + criteria);
		}

		PersistenceUtil.validate(session);
		helper.validate(entities);

		final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
		final HibernateHelper hbnHelper = HibernateHelper.getInstance();
		final Criteria hbnCriteria = hbnHelper.createCriteria(session, UserConnectionBean.class);
		final Criteria hbnCountCriteria = hbnHelper.createCriteria(session, UserConnectionBean.class);

		if (criteria == null) {
			hbnCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, Boolean.FALSE));
			hbnCountCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, Boolean.FALSE));
		}
		else {
			final Boolean archived = criteria.isArchived();
			final EApprovalStatus status = criteria.getStatus();
			final Boolean requested = criteria.isRequested();
			final long userKey = criteria.getUserKey();

			if (userKey > 0) {
				if (requested == null) {
					final Disjunction disjunction = Restrictions.disjunction();

					disjunction.add(Restrictions.eq(IUserConnection.INITIATOR + '.' + IUser.KEY, userKey));
					disjunction.add(Restrictions.eq(IUserConnection.RECIPIENT + '.' + IUser.KEY, userKey));

					hbnCriteria.add(disjunction);
					hbnCountCriteria.add(disjunction);
				}
				else if (requested.booleanValue()) {
					hbnCriteria.add(Restrictions.eq(IUserConnection.INITIATOR + '.' + IUser.KEY, userKey));
					hbnCountCriteria.add(Restrictions
					    .eq(IUserConnection.INITIATOR + '.' + IUser.KEY, userKey));
				}
				else {
					hbnCriteria.add(Restrictions.eq(IUserConnection.RECIPIENT + '.' + IUser.KEY, userKey));
					hbnCountCriteria.add(Restrictions
					    .eq(IUserConnection.RECIPIENT + '.' + IUser.KEY, userKey));
				}
			}

			if (status != null) {
				hbnCriteria.add(Restrictions.like(IUserConnection.APPROVAL_STATUS, status));
				hbnCountCriteria.add(Restrictions.like(IUserConnection.APPROVAL_STATUS, status));
			}

			if (archived == null) {
				hbnCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, Boolean.FALSE));
				hbnCountCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, Boolean.FALSE));
			}
			else {
				hbnCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, archived));
				hbnCountCriteria.add(Restrictions.eq(IUserConnection.ARCHIVED, archived));
			}
		}

		PersistenceUtil.applyCriteria(params, criteria);
		hbnHelper.applyParameters(params, hbnCriteria);

		try {
			hbnCountCriteria.setProjection(Projections.count(IUserConnection.KEY));
			session.flush();

			final long count = ((Number)hbnCountCriteria.uniqueResult()).longValue();

			if (count > 0) {
				entities.addAll(hbnCriteria.list());
			}

			return count;
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List)
	 */
	public void clear(final PersistenceSession session, final List<IQueryParameter> parameters)
	    throws PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Removing entities using session = " + session + ", parameters = " + parameters);
		}

		session.removeEntities(session.findEntities(IUserConnection.class, QUERY_FIND, parameters),
		    null);
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List)
	 */
	public long count(final PersistenceSession session, final List<IQueryParameter> parameters)
	    throws PersistenceException {
		return helper.count(session, parameters, QUERY_FIND_COUNT);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid.common.persistence
	 * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
	 * java.util.List)
	 */
	public long findByCreationDate(final PersistenceSession session, final DateTime date1,
	    final DateTime date2, final List<IUserConnectionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

		try {
			PersistenceUtil.applySorting(params, IUserConnection.CREATION_DATE);

			return helper.findListItemsByDate(session, date1, date2, entities, params,
			    QUERY_FIND_BY_CREATION_DATE, QUERY_FIND_BY_CREATION_DATE_COUNT);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#findByModificationDate(org.infodavid.common.persistence
	 * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
	 * java.util.List)
	 */
	public long findByModificationDate(final PersistenceSession session, final DateTime date1,
	    final DateTime date2, final List<IUserConnectionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

		try {
			PersistenceUtil.applySorting(params, IUserConnection.MODIFICATION_DATE);

			return helper.findListItemsByDate(session, date1, date2, entities, params,
			    QUERY_FIND_BY_MODIFICATION_DATE, QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.UserConnectionDataService#isConnectionOf(org.infodavid.common.
	 * persistence.PersistenceSession, java.lang.Long, java.lang.Long)
	 */
	@Override
	public boolean isConnectionOf(final PersistenceSession session, final Long connectionKey,
	    final Long userKey) throws ServiceException, PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Searching if users are connected using session = " + session + ", userKey = "
			    + userKey + ", connectionKey = " + connectionKey);
		}

		if (connectionKey == null) {
			throw new IllegalArgumentException(IUserConnection.RECIPIENT);
		}

		if (userKey == null) {
			throw new IllegalArgumentException(IUser.KEY);
		}

		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(2);
		boolean result = false;

		params.get(0).setName(IUser.ID).setValue(userKey);
		params.get(1).setName(IUserConnection.RECIPIENT).setValue(connectionKey);

		try {
			session.flush(); // use of native query

			final List<? extends IUserConnection> results =
			    session.findEntities(UserConnectionBean.class, QUERY_FIND_BY_USERS, params);

			result = results != null && results.size() > 0;
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}

		return result;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.UserConnectionDataService#findConnectedByUser(org.infodavid.common
	 * .persistence.PersistenceSession, java.lang.Long, java.util.List,
	 * org.infodavid.common.model.criteria.ISearchCriteria)
	 */
	@Override
	public long findConnectedByUser(final PersistenceSession session, final Long userKey,
	    final List<IUserConnectionListItem> results, final IExtendedSearchCriteria criteria)
	    throws ServiceException, PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Searching connected users by user connection using session = " + session
			    + ", userKey = " + userKey + ", entities = " + results);
		}

		PersistenceUtil.validate(session);
		helper.validate(results);

		if (userKey == null) {
			throw new IllegalArgumentException(IUser.KEY);
		}

		final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(2);

		params.get(0).setName(IUser.ID).setValue(userKey);
		params
		    .get(1)
		    .setName("date1")
		    .setValue(
		        new DateTime().minusMinutes(UserSessionDataService.getInstance()
		            .getSessionIdleTimeInMin()));

		PersistenceUtil.applyCriteria(params, criteria);

		try {
			return helper.findListItems(session, results, params, QUERY_FIND_CONNECTED_BY_USER,
			    QUERY_FIND_CONNECTED_BY_USER_COUNT);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}
}
